import type { WebSocketClient } from '../../net/ws/WsClient';
import { Input } from '../core/input/Input';
import type { InputFrame } from '../core/input/Input';

interface Vector2 {
  x: number;
  y: number;
}

interface GameState {
  players: Record<string, PlayerState>;
  world: WorldState;
  timestamp: number;
  lastProcessedInput: number;
}

interface PlayerState {
  id: string;
  position: Vector2;
  velocity: Vector2;
  rotation: number;
}

interface WorldState {
  objects: GameObject[];
}

interface GameObject {
  id: string;
  type: string;
  position: Vector2;
  state: Record<string, any>;
}

class StateManager {
  private currentState: GameState;
  private wsClient: WebSocketClient;
  private stateBuffer: GameState[] = [];
  private lastSentState: GameState | null = null;
  private predictedStates: GameState[] = [];
  private inputSequence = 0;

  constructor(wsClient: WebSocketClient) {
    this.wsClient = wsClient;
    this.currentState = this.createInitialState();
    this.setupWebSocketHandlers();
  }

  private createInitialState(): GameState {
    return {
      players: {},
      world: {
        objects: []
      },
      timestamp: Date.now(),
      lastProcessedInput: 0
    };
  }

  private setupWebSocketHandlers() {
    this.wsClient.on('state-update', (serverState: GameState) => {
      this.applyServerState(serverState);
    });

    this.wsClient.on('full-state', (fullState: GameState) => {
      this.currentState = fullState;
    });
  }

  public updateLocalState(deltaTime: number, input: Input) {
    // Apply local input prediction
      const inputFrame = Input.getLatestInputFrame();
    if (inputFrame) {
      this.inputSequence++;
      this.applyInput(inputFrame);
      this.currentState.lastProcessedInput = this.inputSequence;
      
      // Store predicted state
      this.predictedStates.push({...this.currentState});
      if (this.predictedStates.length > 10) {
        this.predictedStates.shift();
      }
    }

    this.currentState.timestamp = Date.now();
    this.bufferState();
    this.sendStateUpdate();
  }

  private applyInput(inputFrame: InputFrame) {
    // Apply input to local player state
    const localPlayer = this.currentState.players['local'];
    if (localPlayer) {
      // Update player position based on input
      const speed = 5; // pixels per frame
      if (inputFrame.keys['KeyW']) {
        localPlayer.position.y -= speed;
      }
      if (inputFrame.keys['KeyS']) {
        localPlayer.position.y += speed;
      }
      if (inputFrame.keys['KeyA']) {
        localPlayer.position.x -= speed;
      }
      if (inputFrame.keys['KeyD']) {
        localPlayer.position.x += speed;
      }
    }
  }

  private reconcileWithServer(serverState: GameState) {
    // Find the last processed input acknowledged by server
    const lastAckedInput = serverState.lastProcessedInput;
    
    // Remove acknowledged states from prediction buffer
    this.predictedStates = this.predictedStates.filter(
      state => state.lastProcessedInput > lastAckedInput
    );

    // Apply server state
    this.currentState = {...serverState};

    // Re-apply unacknowledged inputs
    this.predictedStates.forEach(state => {
      const inputFrame = Input.getLatestInputFrame();
      if (inputFrame) {
        this.applyInput(inputFrame);
      }
    });
  }

  private bufferState() {
    this.stateBuffer.push(this.currentState);
    if (this.stateBuffer.length > 10) {
      this.stateBuffer.shift();
    }
  }

  private sendStateUpdate() {
    if (!this.lastSentState) {
      this.wsClient.send('full-state', this.currentState);
    } else {
      const delta = this.calculateDelta(this.lastSentState, this.currentState);
      this.wsClient.send('state-update', delta);
    }
    this.lastSentState = this.currentState;
  }

  private calculateDelta(oldState: GameState, newState: GameState): Partial<GameState> {
    const delta: Partial<GameState> = {};
    
    // Calculate player state differences
    delta.players = {};
    for (const [id, player] of Object.entries(newState.players)) {
      if (!oldState.players[id] || 
          !this.deepEqual(oldState.players[id], player)) {
        delta.players[id] = player;
      }
    }

    // Calculate world object differences
    delta.world = {
      objects: newState.world.objects.filter((obj, index) => {
        return !oldState.world.objects[index] ||
               !this.deepEqual(oldState.world.objects[index], obj);
      })
    };

    return delta;
  }

  private deepEqual(obj1: any, obj2: any): boolean {
    return JSON.stringify(obj1) === JSON.stringify(obj2);
  }

  private applyServerState(serverState: Partial<GameState>) {
    // Apply server state with interpolation/smoothing
    if (serverState.players) {
      Object.assign(this.currentState.players, serverState.players);
    }
    if (serverState.world) {
      this.currentState.world = serverState.world;
    }
  }

  public getCurrentState(): GameState {
    return this.currentState;
  }
}

export default StateManager;
